//
// Created by martin on 7/6/21.
//
#include "common.h"

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <net/if.h>

int socket_create(const char *ip, const int port)
{
    if (ip == NULL || port < 0) {
        return -1;
    }

    /* create socket */
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket error");
        return -1;
    }

    /* set sock option SO_REUSEADDR for re-bind when calling socket failed */
    int op = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op));

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = inet_addr(ip);

    if (bind(sock, (SA *)&local, sizeof (local)) < 0) {
        return -1;
    }

    if (listen(sock, 10) < 0) {
        return -1;
    }

    return sock;
}

int socket_accept(int sock)
{
    struct sockaddr_in peer;
    socklen_t len = sizeof(peer);

    int connfd = accept(sock, (SA *)&peer, &len);
    if (connfd < 0) {
        return -1;
    }

    return connfd;
}

int socket_connect(const char *ip, const int port)
{
    if (ip == NULL || port < 0) {
        return -1;
    }

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        return -1;
    }

    struct sockaddr_in peer;
    peer.sin_family = AF_INET;
    peer.sin_port = htons(port);
    peer.sin_addr.s_addr = inet_addr(ip);

    if (connect(sock, (SA *)&peer, sizeof(peer)) < 0) {
        return -1;
    }

    return sock;
}

int recv_data(int sock, char *buf, int bufsize)
{
    bzero(buf, bufsize);
    ssize_t n = recv(sock, buf, bufsize, 0);
    if (n < 0) {
        perror("recv error");
        return -1;
    }

    return n;
}

#if 0
int send_response(int sock, int code)
{
    int stat_code = htonl(code);

    if (send(sock, &stat_code, sizeof(stat_code), 0) < 0) {
        return -1;
    }

    return 0;
}
#endif

ssize_t writen(int fd, const void *buf, size_t count)
{
    size_t nleft = count; //剩余的需要写入的字节数。
    ssize_t nwritten; //成功写入的字节数。
    char *bufp = (char*) buf; //将缓冲的指针强制转换为字符类型的指针。
    /*
     * 如果剩余需要写入的字节数大于0则继续
     * */
    while (nleft > 0)
    {
        /*
         * ssize_t write(int fd, const void *buf, size_t count);
         * fd为需要写入的文件描述符，buf为字符缓存区，count为需要写入的字节数。
         *
         * */
        if ((nwritten = write(fd, bufp, nleft)) < 0)
        {
            /*
             * 如果是被信号中断的继续读
             * */
            if (errno == EINTR)
                continue;
            return -1;
        } else if (nwritten == 0)
            continue;
        //字符指针推移已经写入成功大小的字节数。
        bufp += nwritten;
        //剩余的字节数。
        nleft -= nwritten;
    }
    return count;
}


int send_reply(int sock, int code, const char *text)
{
    char buf[200] = {0};
    int stat_code;
    stat_code = code; // htons(code);

    if (text == NULL)
        sprintf(buf, "%d \r\n", stat_code);
    else
        sprintf(buf, "%d %s\r\n", stat_code, text);

    writen(sock, buf, strlen(buf));
    return 0;
}

int send_reply_multiline_first(int sock, int code, const char *text)
{
    char buf[200] = {0};
    sprintf(buf, "%d-%s\r\n", code, text);
    writen(sock, buf, strlen(buf));
    return 0;
}

int send_reply_multiline_middle(int sock, const char *text)
{
    char buf[200] = {0};
    char *p = text;
    int leading_digit_count = 0;
    int leading_three_digit = 0;

    while (*p != '\0') {
        if (*p >= '0' && *p <= '9') leading_digit_count++;
        else break;

        if (leading_digit_count >= 3) {
            leading_three_digit = 1;
            break;
        }
        p++;
    }

    /* 行首3个连续数字，插入2个空格,避免与FTP命令的响应混淆 */
    if (leading_three_digit != 0)
        sprintf(buf, "  %s\r\n", text);
    else
        sprintf(buf, "%s\r\n", text);

    writen(sock, buf, strlen(buf));
    return 0;
}

/**
 * 从套接字读取一行数据，没有换行符，认为是错误格式
 *
 * @return
 * - >0 调用成功，返回读取行的字符数
 * - -1 系统调用recv出错
 * - -2 没有找到换行符
 *
 * @note 用于从tcp连接对端, 读取一行数据
 * getline, fgets适用于从文件流读取一行
 * getline由于使用内部static缓存，不适用于多线程环境
 *
 * 如果直接把全部缓存内容一次读取出来，如果换行符('\n')之后还有别的内容，就无法再放回去。
 * 而多读的内容，用户缓存又难以维护
 */
#if 0
ssize_t readline(int sock, void *ptr, size_t maxline)
{
    ssize_t nread;
    ssize_t i;
    ssize_t ret;
    size_t nleft = maxline;
    char *pbuf = ptr;

    if (ptr == NULL || maxline <= 0) return 0;

    while (nleft > 0) {
        /* 先用recv + MSG_PEEK 标志，拷贝socket接收缓存内容到pbuf
         * （但是不会导致socket缓存内容变化），看是否有换行符'\n' */
        nread = recv(sock, pbuf, nleft, MSG_PEEK);
        if (nread < 0) { /* 出错 */
            return nread;
        } else if (nread == 0) { /* nread == 0: 对端shutdown */
            return nread;
        }

        for (i = 0; i < nread; ++i) {
            if (pbuf[i] == '\n') { /* 已经找到换行符 */
                ret = readn(sock, pbuf, i + 1); /* pbuf[0..i]共i + 1个字符 */
                return ret;
            }
        }

        nleft -= nread;
        pbuf += nread;
    }

    return -2;  /* 读取maxline个字符，但没有换行符 */
}

#else

ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
    while (1)
    {
        /*
         * ssize_t recv(int sockfd, void *buf, size_t len, int flags);
         * sockfd 套接字
         * len 需要读取的长度
         * MSG_PEEK只从队列中查看，但不取出。
         * 返回接受到的字节的长度，失败返回-1，接受到关闭信号返回0；
         * */
        int ret = recv(sockfd, buf, len, MSG_PEEK);
        /*
         * 如果被信号中断了，继续
         * */
        if (ret == -1 && errno == EINTR)
            continue;
        return ret;
    }
}

ssize_t readn(int fd, void *buf, size_t count)
{
    //size_t 在x64下为 unsigned long 类型， 在x86下为 unsigned int 类型
    size_t nleft = count; //将count接过来 ，个数
    ssize_t nread;
    char *bufp = (char*) buf; //将空指针类型转换为char类型指针。
    while (nleft > 0)
    {
        /*ssize_t read(int fd, void *buf, size_t count);
         * 从文件描述符fd中读取count字节存到buf中
         * 返回读取字节数的个数。
         * */
        if ((nread = read(fd, bufp, nleft)) < 0)
        {
            /*
             * 如果是被信号中断的继续读
             * */
            if (errno == EINTR)
                continue;
            return -1;
        }
            /*
             * 如果输入的读取个数为0，那么返回的读取个数为0
             * 不执行任何操作。
             * nleft为剩余的需要读取的字节个数。
             * 如果为0，说明读到文件尾，
             *
             * */
        else if (nread == 0)
            return count - nleft;
        bufp += nread; //将字符指针向前推进已成功读取字符数的大小单位。
        nleft -= nread; //剩余的个数减去已经成功读取的字节数。
    }
    return count;
}

ssize_t readline(int sockfd, void *buf, size_t maxline)
{
    int ret;
    int nread;
    char *bufp = buf;
    int nleft = maxline;
    while (1)
    {
        ret = recv_peek(sockfd, bufp, nleft);
        if (ret < 0)
            return ret;
        else if (ret == 0)
            return ret;

        nread = ret;
        int i;
        for (i=0; i<nread; i++)
        {
            if (bufp[i] == '\n')
            {
                ret = readn(sockfd, bufp, i+1);
                if (ret != i+1)
                    exit(EXIT_FAILURE);

                return ret;
            }
        }

        if (nread > nleft)
            exit(EXIT_FAILURE);

        nleft -= nread;
        ret = readn(sockfd, bufp, nread);
        if (ret != nread)
            exit(EXIT_FAILURE);

        bufp += nread;
    }

    return -1;
}
#endif

/**
 * 从标准输入读取一行数据
 */
void read_input(char *buf, int bufsize)
{
    bzero(buf, bufsize);

    /* 从标准输入读取一行文本，并将第一个出现的换行符'\n'替换为null终结符'\0' */
    if (fgets(buf, bufsize, stdin) != NULL) {
        char *n = strchr(buf, '\n');  /* 定位指定字符'\n'在第一次在字符串buf中出现的位置 */
        if (n != NULL) {
            *n = '\0';
        }
    }
}

/**
 * 从fd指向文件读取最大n个字节的数据，存入ptr指向的缓存
 */
ssize_t readn_my(int fd, void *ptr, size_t n)
{
    size_t nleft = n;
    ssize_t nread;
    char *cp = ptr;

    if (ptr == NULL) return 0;

    while (nleft > 0) {
        if((nread = read(fd, cp, nleft)) < 0) { /* error */
            if (errno == EINTR) { /* 如果是被信号中断，则继续读 */
                continue;
            }
            return -1;
        } else if (nread == 0) { /* EOF */
            break;
        }

        nleft -= nread;
        cp += nread;
    }

    return (ssize_t)(n - nleft);
}

/*
 * 去除空白字符, 替换成空字符
 */
void trimstr(char *str, int n)
{
    int i;

    for (i = 0; i < n; ++i) {
        if (isspace(str[i])) {
            str[i] = 0;
        }
    }
}

/**
 * 获取本地ip地址, 排除"127.0.0.1"
 * @note ioctl非POSIX.1内容, 各系统对其实现也不尽相同, 不同系统上实现可能不一样.
 * 方法是通过ioctl获取本地mac地址的配置信息, 然后将地址信息
 */
int getlocalipaddr(char *ipbuf)
{
    int sockfd;
    struct ifconf ifconf;
    struct ifreq *ifreq;
    char buf[512];
    char *ip;
    struct sockaddr_in *addrp;
    int i;

    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket failed");
        return -1;
    }

    ifconf.ifc_len = sizeof(buf);
    ifconf.ifc_buf = buf;
    ioctl(sockfd, (int)SIOCGIFCONF, &ifconf); /* 获取所有接口信息 */

    close(sockfd);

    ifreq = (struct ifreq *)buf;
    for (i = (ifconf.ifc_len / sizeof(struct ifreq)); i > 0; --i) {
        addrp = (struct sockaddr_in *)&(ifreq->ifr_addr);
        ip = inet_ntoa(addrp->sin_addr); /* ip -> static buf */

        if (strcmp(ip, "127.0.0.1") == 0) { /* 排除127.0.0.1 */
            ++ifreq;
            continue;
        }

        if (ipbuf != NULL) {
            strcpy(ipbuf, ip);
        }
        ++ifreq;
    }

    return 0;
}